package org.dromara.common.redis.manager;

import org.dromara.common.core.utils.SpringUtils;
import org.springframework.cache.Cache;

import java.util.concurrent.Callable;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：Cache 装饰器模式(用于扩展 Caffeine 一级缓存)
 */
/**
 * CaffeineCacheDecorator 类是作为缓存的装饰器实现，它使用 Caffeine 作为底层缓存技术
 * 该类的目的是提供一种灵活的方式来对缓存进行装饰，以便在不修改原有缓存实现的情况下增加新的功能
 * 例如，可以通过这个装饰器添加缓存失效时的自动刷新逻辑，或者对缓存项的过期时间进行自定义设置
 */
public class CaffeineCacheDecorator implements Cache {

    /**
     * 使用Caffeine实现的缓存对象，通过Spring上下文获取
     */
    private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
        CAFFEINE = SpringUtils.getBean("caffeine");

    /**
     * 被装饰的缓存对象
     */
    private final Cache cache;

    /**
     * 构造函数，初始化装饰的缓存对象
     *
     * @param cache 被装饰的缓存对象，不能为空
     */
    public CaffeineCacheDecorator(Cache cache) {
        this.cache = cache;
    }

    /**
     * 获取缓存的名称
     *
     * @return 缓存的名称
     */
    @Override
    public String getName() {
        return cache.getName();
    }

    /**
     * 获取缓存的原生实现对象
     *
     * @return 缓存的原生实现对象
     */
    @Override
    public Object getNativeCache() {
        return cache.getNativeCache();
    }

    /**
     * 根据缓存名称和给定的键生成一个唯一键
     *
     * @param key 原始键
     * @return 唯一键
     */
    public String getUniqueKey(Object key) {
        return cache.getName() + ":" + key;
    }

    /**
     * 根据键获取缓存中的值
     * 使用自定义的键获取策略从缓存中获取值
     * 如果键不存在于当前缓存中，则从底层存储中加载
     *
     * @param key 键，用于标识缓存项
     * @return ValueWrapper 封装的缓存值
     */
    @Override
    public ValueWrapper get(Object key) {
        // 使用CAFFEINE加载策略根据键获取值，如果当前缓存中没有，则从底层cache中加载
        Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key));
        // 将获取到的对象转换为ValueWrapper类型并返回
        return (ValueWrapper) o;
    }

    /**
     * 根据键和指定类型获取缓存值，如果不存在，则尝试从原始数据源中获取
     *
     * @param key 键
     * @param type 指定的返回类型
     * @return 获取到的缓存值，经过类型转换后返回
     * 注：上一个版本是没有@Override注解的
     */
     @Override
    @SuppressWarnings("unchecked")
    public <T> T get(Object key, Class<T> type) {
        Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type));
        return (T) o;
    }


    /**
     * 将指定键值对放入缓存，并清除与键相关联的任何现有缓存项
     *
     * @param key 键
     * @param value 值
     */
    @Override
    public void put(Object key, Object value) {
        CAFFEINE.invalidate(getUniqueKey(key));
        cache.put(key, value);
    }

    /**
     * 如果键不存在，则将指定值放入缓存，并清除与键相关联的任何现有缓存项
     *
     * @param key 键
     * @param value 值
     * @return 如果键不存在且值被放入缓存，则返回null；否则返回键已存在的值
     * 注：上一个版本是没有@Override注解的
     */
     @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        CAFFEINE.invalidate(getUniqueKey(key));
        return cache.putIfAbsent(key, value);
    }

    /**
     * 从缓存中移除指定键的条目
     *
     * @param key 要移除的条目的键
     */
    @Override
    public void evict(Object key) {
        // 尝试从缓存中移除指定键的条目
        evictIfPresent(key);
    }

    /**
     * 如果缓存中存在指定键的条目，则尝试从缓存中移除该条目
     * @param key 要移除的缓存条目的键
     * @return 如果缓存中存在指定键的条目并且被成功移除，则返回true；否则返回false
     * 注：上一个版本是没有@Override注解的
     */
     @Override
    public boolean evictIfPresent(Object key) {
        boolean b = cache.evictIfPresent(key);
        if (b) {
            // 当缓存条目成功被移除时，也在CAFFEINE缓存中移除对应的键
            CAFFEINE.invalidate(getUniqueKey(key));
        }
        return b;
    }

    /**
     * 清空整个缓存
     */
    @Override
    public void clear() {
        cache.clear();
    }

    /**
     * 使缓存无效，但不清理缓存内容
     * @return 如果缓存成功被置为无效，则返回true；否则返回false
     * 注：上一个版本是没有@Override注解的
     */
     @Override
    public boolean invalidate() {
        return cache.invalidate();
    }

    /**
     * 从缓存中获取指定键的值如果键不存在，则使用valueLoader加载值并将其存储在缓存中
     * @param key 要从缓存中获取的值的键
     * @param valueLoader 用于加载键对应值的可调用对象
     * @param <T> 值的类型
     * @return 缓存中的值，或者通过valueLoader加载并缓存的值
     */
    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        // 使用CAFFEINE缓存逻辑获取或加载缓存值
        Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, valueLoader));
        return (T) o;
    }

}
